package com.xlabs.client;

import java.util.List;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.requestfactory.shared.EntityProxyChange;
import com.google.gwt.requestfactory.shared.Receiver;
import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.WriteOperation;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.xlabs.client.DTO.ReplyDTO;
import com.xlabs.client.DTO.TransferDTO;
import com.xlabs.client.Elements.TextLink;
import com.xlabs.client.richtext.ImageButton;
import com.xlabs.client.shared.JeeqsRequestFactory;
import com.xlabs.client.shared.QuestionProxy;
import com.xlabs.client.shared.QuestionRequest;
import com.xlabs.client.shared.ReplyProxy;
import com.xlabs.client.shared.ReplyRequest;

/**
 * Conversation view class.
 */
public class Conversation extends Composite
{
	private static final String ISNOTANSWER_IMAGE = "notAnswer.gif";

	private static final String ISANSWER_IMAGE = "answer.gif";

	/**
	 * Number of responses to show in one page of conversation.
	 */
	private static final int PAGE_SIZE = 10;

	/**
	 * ConversationPanel.
	 */
	private VerticalPanel conversationPanel = new VerticalPanel();

	/**
	 * Conversation scroll panel.
	 */
	private ScrollPanel conversationScroll = new ScrollPanel();
	
	/**
	 * Paging panel.
	 */
	private HorizontalPanel pagingPanel = new HorizontalPanel();
	
	/**
	 * Conversation grid.
	 */
	protected Grid m_conversationGrid = new Grid(PAGE_SIZE, 1);

	/**
	 * Parent question.
	 */
	private QuestionProxy question;
	
	/**
	 * The answer image button that corresponds to the current answer.
	 */
	private ImageButton currentAnswerImage;

	/**
	 * Button to navigate to previous page of conversation.
	 */
	private ImageButton prevPageResponsesButton;

	/**
	 * Navigate button to next page of responses.
	 */
	private ImageButton nextPageRespnsesButton;
	
	/**
	 * Next page reply createdOn index.
	 */
    protected String nextPageReplyCreatedOnIndex;
    
	/**
	 * first reply createdOn index.
	 */
    protected String firstReplyCreatedOnIndex;

	/**
	 * Initializes Conversation. Wires the elements to elements and set the styles.
	 */
	public Conversation()
	{
		initWidget(conversationPanel);
		addStyleName("conversationPanel");
		conversationPanel.add(pagingPanel);
		conversationPanel.add(conversationScroll);
		conversationScroll.addStyleName("conversationScrollPanel");
		m_conversationGrid.addStyleName("conversationGrid");
		initPagingPanel();
	}
	

	/**
	 * Initializes paging panel.
	 */
	private void initPagingPanel() 
	{
		pagingPanel.addStyleName("pagingPanel");
		
        prevPageResponsesButton = new ImageButton(ImageConstants.PREVIOUS_PAGE_ICON_PNG, "");
        nextPageRespnsesButton = new ImageButton(ImageConstants.NEXT_PAGE_ICON_PNG, "");
        
        pagingPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
        prevPageResponsesButton.setUserStyleName(CSSConstants.PAGING_BUTTON);
        pagingPanel.add(prevPageResponsesButton);
        pagingPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
        nextPageRespnsesButton.setUserStyleName(CSSConstants.PAGING_BUTTON);
        pagingPanel.add(nextPageRespnsesButton);
        
        prevPageResponsesButton.addClickHandler(new ClickHandler() 
        {
			public void onClick(ClickEvent event) 
			{
				showPreviousPage();
			}
		});
        
        nextPageRespnsesButton.addClickHandler(new ClickHandler() 
        {
			public void onClick(ClickEvent event) 
			{
				showNextPage();
			}
		});
	}

	/**
	 * Shows conversation for the given parent.
	 * 
	 * @param question
	 *            parent question proxy.
	 */
	public void showfirstPage(QuestionProxy question)
	{
		this.question = question;
		
		registerProxyListener();
		
		JeeqsRequestFactory jeeqsFactory = Skulgia.getJeeqsRequestFactory();
		ReplyRequest request = jeeqsFactory.replyRequest();

		Request<List<ReplyProxy>> getQuestionReplies = request.getRepliesByIndex(
				question.getId(),
				String.valueOf(0),
				"\uffffffffffffffffffffffffffffffffffffffffffffffffff", // 50 characters is the maximum length of this index
				TransferDTO.RIGHT_DIRECTION,
				Conversation.PAGE_SIZE).with("answerToQuestion");
		
		getQuestionReplies.fire(new Receiver<List<ReplyProxy>>()
		{

			@Override
			public void onSuccess(List<ReplyProxy> replies)
			{
				if (replies.size() == 0)
				{
					return;
				}
    			
				boolean hasNext = replies.size() == Conversation.PAGE_SIZE + 1;
				nextPageReplyCreatedOnIndex = hasNext ? replies.get(Conversation.PAGE_SIZE).getCreatedOnIndex() : null;
				firstReplyCreatedOnIndex = replies.get(0).getCreatedOnIndex();
        		if (hasNext) replies.remove(Conversation.PAGE_SIZE);

				populateConversationView(m_conversationGrid, replies, 0);
				refreshConversationView(m_conversationGrid);
			}
		});
	}

	private void registerProxyListener()
	{
		EntityProxyChange.registerForProxyType(
				Skulgia.getEventBus(), 
				QuestionProxy.class,
		        new EntityProxyChange.Handler<QuestionProxy>() {
		          public void onProxyChange(EntityProxyChange<QuestionProxy> event) {
		            Skulgia.getJeeqsRequestFactory().questionRequest().findQuestion(Conversation.this.question.getId()).fire(new Receiver<QuestionProxy>()
					{

						@Override
						public void onSuccess(QuestionProxy response)
						{
							Conversation.this.question = response;
						}
					});
		          }
		        });		
	}


	/**
	 * Queries next page of responses.
	 */
	protected void showNextPage()
	{
		CommentingServiceAsync commentingService = Skulgia.getCommentingService();
		AsyncCallback<TransferDTO> nextPageCallBack = new AsyncCallback<TransferDTO>()
		{
			
			/**
			 * onSuccess.
			 * 
			 * @param replies
			 *            Object returned from server.
			 */
			public void onSuccess(TransferDTO response)
			{
//				handleServerResponseForQuestionReplies(response);
			}
			
			public void onFailure(Throwable caught)
			{
				// TODO Auto-generated method stub
				
			}
		};
		
		commentingService.getQuestionResponsesByIndex(
				question.getId(),
				nextPageReplyCreatedOnIndex,
				"\uffffffffffffffffffffffffffffffffffffffffffffffffff", // 50 characters is the maximum length of this index
				TransferDTO.RIGHT_DIRECTION,
				Conversation.PAGE_SIZE, 
				nextPageCallBack);
	}


	/**
	 * Queries for previous page of responses.
	 */
	protected void showPreviousPage()
	{
		CommentingServiceAsync commentingService = Skulgia.getCommentingService();
		AsyncCallback<TransferDTO> prevPageCallBack = new AsyncCallback<TransferDTO>()
		{
			
			/**
			 * onSuccess.
			 * 
			 * @param replies
			 *            Object returned from server.
			 */
			public void onSuccess(TransferDTO response)
			{
//				handleServerResponseForQuestionReplies(response);
			}
			
			public void onFailure(Throwable caught)
			{
				// TODO Auto-generated method stub
				
			}
		};
		
		commentingService.getQuestionResponsesByIndex(
				question.getId(),
				"",
				firstReplyCreatedOnIndex,
				TransferDTO.LEFT_DIRECTION,
				Conversation.PAGE_SIZE, 
				prevPageCallBack);
	}

	/**
	 * Creates a grid from the given list of reply DTO's.
	 * @param grid the grid to render responses in.
	 * @param replies List of replies.
	 * @param level The level at which the conversation is to be populated.
	 */
	protected void populateConversationView(Grid grid, List<ReplyProxy> replies, int level)
	{
		grid.clear();
		
		int index = 0;
		
		for (ReplyProxy dto : replies)
		{
			addReplyToConversationView(dto, grid, index++, level);						
		}
	}


	/**
	 * Creates row in the grid based on given reply at given index.
	 * 
	 * @param reply
	 *            Input reply.
	 * @param grid 
	 *            grid to render responses in.
	 * @param index
	 *            Index of row.
	 * @param level
	 *            The level of this reply in hierarchy.
	 */
	private void addReplyToConversationView(ReplyProxy reply, Grid grid, final Integer index, int level)
	{
		HorizontalPanel conversationRowPanel = new HorizontalPanel();
		conversationRowPanel.addStyleName("conversationRowPanel");
		grid.setWidget(index, 0, conversationRowPanel);
		grid.getCellFormatter().addStyleName(index, 0, "GridCell");
		
		VerticalPanel replyTextPanel = getReplyTextPanel(reply, index, level);
		conversationRowPanel.add(replyTextPanel);
		
		conversationRowPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
		Label userLabel = new Label(reply.getCreatedByName());
		conversationRowPanel.add(userLabel);
		
		Widget replyButton = getReplyButton(reply, grid, index, level);
		if (replyButton != null)
		{
			conversationRowPanel.add(replyButton);
		}
		
		Widget answerButton = getAnswerButton(reply);
		conversationRowPanel.add(answerButton);
	}


	/**
	 * Adds the answer button to a conversation row.
	 * @param reply The reply in row.
	 */
	private Widget getAnswerButton(final ReplyProxy reply)
	{
		final String replyId = reply.getId();
		final ImageButton answerButton = new ImageButton("", "");
		if (reply.getIsAnswer())
		{
			answerButton.setUrl(ISANSWER_IMAGE);
			currentAnswerImage = answerButton;
		}
		else
		{
			answerButton.setUrl(ISNOTANSWER_IMAGE);
		}
		
		answerButton.addClickHandler(new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				String existingAnswerId = question.getAnswerId();
				
				if (!replyId.equals(existingAnswerId))
				{
					QuestionRequest questionRequest = Skulgia.getJeeqsRequestFactory().questionRequest();
					QuestionProxy mutableQuestion = questionRequest.edit(question);
					mutableQuestion.setAnswerId(reply.getId());
					questionRequest.persist().using(mutableQuestion).fire();
					
					ReplyRequest replyRequest = Skulgia.getJeeqsRequestFactory().replyRequest();
					ReplyProxy mutableReply = replyRequest.edit(reply);
					mutableReply.setAnswerForId(question.getId());
					replyRequest.persist().using(mutableReply).fire();
					
					if (question.getAnswerId() != null)
					{
						replyRequest = Skulgia.getJeeqsRequestFactory().replyRequest();
						replyRequest.findReply(existingAnswerId).fire(new Receiver<ReplyProxy>()
						{

							@Override
							public void onSuccess(ReplyProxy response)
							{
								ReplyRequest replyRequest = Skulgia.getJeeqsRequestFactory().replyRequest();
								ReplyProxy mutableExistingAnswer = replyRequest.edit(response);
								mutableExistingAnswer.setAnswerForId(null);
								replyRequest.persist().using(mutableExistingAnswer).fire();
							}
						});
					}
					
					Skulgia.getEventBus().fireEventFromSource(
							new EntityProxyChange<QuestionProxy>(mutableQuestion, WriteOperation.UPDATE), QuestionProxy.class);
					
					answerButton.setUrl(ISANSWER_IMAGE);
					if (currentAnswerImage != null)
					{
						currentAnswerImage.setUrl(ISNOTANSWER_IMAGE);
					}
					currentAnswerImage = answerButton;
					
					Questions.updateSelectedQuestion(true);
				}
				else
				{
					QuestionRequest questionRequest = Skulgia.getJeeqsRequestFactory().questionRequest();
					QuestionProxy mutableQuestion = questionRequest.edit(question);
					mutableQuestion.setAnswerId(null);
					questionRequest.persist().using(mutableQuestion).fire();
					
					Skulgia.getEventBus().fireEventFromSource(
							new EntityProxyChange<QuestionProxy>(mutableQuestion, WriteOperation.UPDATE), QuestionProxy.class);
					
					answerButton.setUrl(ISNOTANSWER_IMAGE);
					currentAnswerImage = null;
					
					Questions.updateSelectedQuestion(false);
				}
			} 			
		});
		
		return answerButton;
	}

	/**
	 * Adds a reply button to the current conversation row.
	 * @param reply The current reply in the conversation.
	 * @param grid The grid on which the conversation is being rendered.
	 * @param index The index of the reply in the grid.
	 * @param level The level of the reply.
	 * @param conversationRowPanel The panel in which the button is to be added.
	 */
	private Widget getReplyButton(
			ReplyProxy reply, 
			Grid grid,
			final Integer index, 
			int level)
	{
		ImageButton replyButton = null;
		
		if (level == 0) //Show reply button only at root level
		{
			replyButton = new ImageButton("Email-Reply-icon.png", "");
			
			replyButton.addClickHandler(this.new InnerReplyClickHandler(index, grid, reply.getId()));
		}
		
		return replyButton;
	}


	/**
	 * creates the reply text panel.
	 * @param reply the reply.
	 * @param index index in the grid. 
	 * @param level level of reply.
	 * @return reply text panel.
	 */
	private VerticalPanel getReplyTextPanel(final ReplyProxy reply, final Integer index, int level)
	{
		String indentation = getIndentation(level);
		Label textLabel = new Label(indentation + reply.getReplyText());
		VerticalPanel replyTextPanel = new VerticalPanel();
		
		replyTextPanel.add(textLabel);
		
		if (reply.getReplyCount() > 0)
		{
			final TextLink repliesLink = new TextLink("> " + reply.getReplyCount() + " replies ");
			
			repliesLink.addClickHandler(new ClickHandler() 
			{
				public void onClick(ClickEvent event) 
				{
					showInnerReplies(index, reply);
					repliesLink.setVisible(false);
				}
			});
			
			replyTextPanel.add(repliesLink);
		}
		return replyTextPanel;
	}

	/**
	 * Gets indentation to show for the given level of reply. 
	 * @param level Level of reply. The direct answers to a question will have level 0.
	 * @return The indentation string. 
	 */
	private String getIndentation(int level)
	{
		String indentation = "";
		
		for (int i = 0; i < level; i++)
		{
			indentation = indentation + "...";
		}
		return indentation;
	}

	/**
	 * Shows the replies to a reply.
	 * @param index The index at which the reply exists.
	 * @param parentReply parent reply.
	 */
	protected void showInnerReplies(final int index, ReplyProxy parentReply) 
	{
		CommentingServiceAsync commentingService = Skulgia.getCommentingService();
		
		AsyncCallback<TransferDTO> showInnerRepliesCallBack = new AsyncCallback<TransferDTO>()
		{
			
			/**
			 * onSuccess.
			 * 
			 * @param replies
			 *            Object returned from server.
			 */
			public void onSuccess(TransferDTO response)
			{
				handleServerResponseForInnerReplies(response, index);
			}
			
			public void onFailure(Throwable caught)
			{
				// TODO Auto-generated method stub
				
			}
		};
		
		commentingService.getInnerRepliesByIndex(
				parentReply.getId(),
				String.valueOf(0),
				"\uffffffffffffffffffffffffffffffffffffffffffffffffff", // 50 characters is the maximum length of this index
				TransferDTO.RIGHT_DIRECTION,
				Conversation.PAGE_SIZE, 
				showInnerRepliesCallBack);
	}

	/**
	 * Gets conversation scroll.
	 * 
	 * @return conversation scroll.
	 */
	public ScrollPanel getConversationScroll()
	{
		return conversationScroll;
	}

	/**
	 * Gets conversation grid.
	 * 
	 * @return conversation grid.
	 */
	public Grid getConversationGrid()
	{
		return m_conversationGrid;
	}

	/**
	 * Sets conversationGrid.
	 * 
	 * @param conversationGrid
	 *            Conversation grid.
	 */
	public void refreshConversationView(Grid conversationGrid)
	{
		conversationScroll.clear();
		conversationScroll.add(conversationGrid);
	}
	
	/**
	 * Handles server response for inner replies. 
	 * @param response The response dto.
	 * @param index The index at which the inner replies are to be inserted.
	 */
	@SuppressWarnings("unchecked")
	protected void handleServerResponseForInnerReplies(TransferDTO response, int index)
	{
		List<ReplyDTO> dTOs = (List<ReplyDTO>) response.getValue(TransferDTO.RESULT_KEY);
		
		Grid innerRepliesGrid = new Grid(dTOs.size(), 1);
		innerRepliesGrid.addStyleName("innerRepliesGrid");

		VerticalPanel replyWithInnerReplies = new VerticalPanel();
		replyWithInnerReplies.addStyleName("innerRepliesPanel");
		
		replyWithInnerReplies.add(m_conversationGrid.getWidget(index, 0));
		replyWithInnerReplies.add(innerRepliesGrid);
		m_conversationGrid.setWidget(index, 0, replyWithInnerReplies);
		
//		populateConversationView(innerRepliesGrid, dTOs, 1);
	}
	
	/**
	 * ReplyListener class.
	 */
	class InnerReplyClickHandler implements ClickHandler
	{

		/**
		 * index of reply.
		 */
		private int index;

		/**
		 * Comments grid.
		 */
		private Grid grid;

		/**
		 * Question key.
		 */
		private String parentReplyKey;

		/**
		 * Initializes an instance of ReplyListener.
		 * 
		 * @param index
		 *            index of row clicked.
		 * @param conversationGrid
		 *            The comments grid.
		 * @param parentReplyKey
		 *            key of the question.
		 */
		public InnerReplyClickHandler(int index, Grid conversationGrid,	String parentReplyKey)
		{
			this.index = index;
			this.grid = conversationGrid;
			this.parentReplyKey = parentReplyKey;
		}

		/**
		 * Onclick.
		 * 
		 * @param widget
		 *            widget.
		 */
		public void onClick(ClickEvent event)
		{
			((ImageButton) event.getSource()).setVisible(false);
			
			VerticalPanel replyBox = new VerticalPanel();
			final TextArea replyArea = new TextArea();
			
			replyArea.setSize("300px", "100px");
			replyBox.add(grid.getWidget(index, 0));
			replyBox.add(replyArea);

			Button submitButton = new Button("Reply", new ClickHandler()
			{
				
				public void onClick(ClickEvent event)
				{
					((Button) event.getSource()).setEnabled(false);
					
					CommentingServiceAsync myService = Skulgia.getCommentingService();
					
					myService.posInnertReply(
							replyArea.getText(),
							InnerReplyClickHandler.this.parentReplyKey,
							question.getId(),
							new AsyncCallback<ReplyDTO>()
							{
								public void onFailure(Throwable throwable)
								{
									replyArea.setText("Failed to post Reply : "
											+ replyArea.getText()
											+ " Because of " + throwable);
								}
		
								public void onSuccess(ReplyDTO o)
								{
									replyArea.setText("Posted Reply : " + replyArea.getText());
									replyArea.setReadOnly(true);
								}
							});
				}
			});

			replyBox.add(submitButton);
			grid.setWidget(index, 0, replyBox);
		}
	}
}
